package es.udc.pfc.davidparedes.model.userservice;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import es.udc.pfc.davidparedes.model.admin.Admin;
import es.udc.pfc.davidparedes.model.admin.AdminDao;
import es.udc.pfc.davidparedes.model.director.Director;
import es.udc.pfc.davidparedes.model.director.DirectorDao;
import es.udc.pfc.davidparedes.model.mission.MissionDao;
import es.udc.pfc.davidparedes.model.receptionist.Receptionist;
import es.udc.pfc.davidparedes.model.receptionist.ReceptionistDao;
import es.udc.pfc.davidparedes.model.shift.ShiftDao;
import es.udc.pfc.davidparedes.model.store.Store;
import es.udc.pfc.davidparedes.model.store.StoreDao;
import es.udc.pfc.davidparedes.model.task.TaskDao;
import es.udc.pfc.davidparedes.model.universe.Universe;
import es.udc.pfc.davidparedes.model.universe.UniverseDao;
import es.udc.pfc.davidparedes.model.universemanager.UniverseManager;
import es.udc.pfc.davidparedes.model.universemanager.UniverseManagerDao;
import es.udc.pfc.davidparedes.model.userprofile.UserProfile;
import es.udc.pfc.davidparedes.model.userprofile.UserProfileDao;
import es.udc.pfc.davidparedes.model.userservice.util.PasswordEncrypter;
import es.udc.pfc.davidparedes.model.util.DuplicateInstanceException;
import es.udc.pfc.davidparedes.model.util.InstanceNotFoundException;
import es.udc.pfc.davidparedes.model.vendor.Vendor;
import es.udc.pfc.davidparedes.model.vendor.VendorDao;

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserProfileDao userProfileDao;
    
    @Autowired
    private ReceptionistDao receptionistDao;
    
    @Autowired
    private VendorDao vendorDao;
    
    @Autowired
    private UniverseManagerDao universeManagerDao;
    
    @Autowired
    private DirectorDao directorDao;
    
    @Autowired
    private AdminDao adminDao;
    
    @Autowired
    private UniverseDao universeDao;
    
    @Autowired
    private TaskDao taskDao;
    
    @Autowired
    private MissionDao missionDao;
    
    @Autowired
    private ShiftDao shiftDao;
    
    @Autowired
    private StoreDao storeDao;
    
    public Receptionist registerReceptionist(String loginName, String clearPassword,
            UserProfileDetails userProfileDetails, Calendar contractTermination)
            throws DuplicateInstanceException, InstanceNotFoundException {

        try {
        	// Is it searching inside all types of users? (Receptionist, Vendor...)
            userProfileDao.findByLoginName(loginName);
            throw new DuplicateInstanceException(loginName,
                    UserProfile.class.getName());
        } catch (InstanceNotFoundException e) {
            String encryptedPassword = PasswordEncrypter.crypt(clearPassword);
            Store store = storeDao.find(userProfileDetails.getStore());

            Receptionist receptionist = new Receptionist(loginName,
                    encryptedPassword, userProfileDetails.getFirstName(),
                    userProfileDetails.getLastName(), userProfileDetails
                        .getEmail(), userProfileDetails
                        .getNif(), store, contractTermination);

            receptionistDao.save(receptionist);
            return receptionist;
        }

    }
    
    public void updateReceptionist(Long userProfileId, String firstName,
    		String lastName, String email, String nif, Calendar contractTermination)
    		throws InstanceNotFoundException {
    	
    	Receptionist receptionist = findReceptionist(userProfileId);
    	receptionist.setFirstName(firstName);
    	receptionist.setLastName(lastName);
    	receptionist.setEmail(email);
    	receptionist.setNif(nif);
    	receptionist.setContractTermination(contractTermination);
    	
    }
    
    public void deactivateReceptionist(Long userProfileId)
    		throws InstanceNotFoundException {
    	
    	Receptionist receptionist = findReceptionist(userProfileId);
    	
    	Calendar today = getToday();
    	
    	receptionist.setContractTermination(today);
    	
    }
    
    public Vendor registerVendor(String loginName, String clearPassword,
            UserProfileDetails userProfileDetails, Calendar contractTermination,
            int baseHours, Long universeId)
            throws DuplicateInstanceException, InstanceNotFoundException {

        try {
            userProfileDao.findByLoginName(loginName);
            throw new DuplicateInstanceException(loginName,
                    UserProfile.class.getName());
        } catch (InstanceNotFoundException e) {
            String encryptedPassword = PasswordEncrypter.crypt(clearPassword);
            Store store = storeDao.find(userProfileDetails.getStore());
            Universe universe = universeDao.find(universeId);

            Vendor vendor = new Vendor(loginName,
                    encryptedPassword, userProfileDetails.getFirstName(),
                    userProfileDetails.getLastName(), userProfileDetails
                        .getEmail(), userProfileDetails
                        .getNif(), store, contractTermination,
                        baseHours, universe);

            vendorDao.save(vendor);
            return vendor;
        }

    }
    
    public void updateVendor(Long userProfileId, String firstName,
    		String lastName, String email, String nif, Calendar contractTermination,
            int baseHours)
            throws InstanceNotFoundException {

    		Vendor vendor = findVendor(userProfileId);
	    	vendor.setFirstName(firstName);
	    	vendor.setLastName(lastName);
	    	vendor.setEmail(email);
	    	vendor.setNif(nif);
	    	vendor.setContractTermination(contractTermination);
	    	vendor.setBaseHours(baseHours);
    }
    
    public void deactivateVendor(Long userProfileId) throws InstanceNotFoundException {
    	
    	Vendor vendor = findVendor(userProfileId);
    	Calendar today = getToday();
    	vendor.setContractTermination(today);
    	vendor.setUniverse(null);
    }
    
    public UniverseManager registerUniverseManager(String loginName, String clearPassword,
            UserProfileDetails userProfileDetails, Calendar contractTermination)
            throws DuplicateInstanceException, InstanceNotFoundException {

        try {
            userProfileDao.findByLoginName(loginName);
            throw new DuplicateInstanceException(loginName,
                    UserProfile.class.getName());
        } catch (InstanceNotFoundException e) {
            String encryptedPassword = PasswordEncrypter.crypt(clearPassword);
            Store store = storeDao.find(userProfileDetails.getStore());

            UniverseManager manager = new UniverseManager(loginName,
                    encryptedPassword, userProfileDetails.getFirstName(),
                    userProfileDetails.getLastName(), userProfileDetails
                        .getEmail(), userProfileDetails
                        .getNif(), store, contractTermination);

            universeManagerDao.save(manager);
            return manager;
        }

    }
    
    public void updateUniverseManager(Long userProfileId, String firstName,
    		String lastName, String email, String nif, Calendar contractTermination)
            throws InstanceNotFoundException {

	    	UniverseManager manager = findUniverseManager(userProfileId);
	    	manager.setFirstName(firstName);
	    	manager.setLastName(lastName);
	    	manager.setEmail(email);
	    	manager.setNif(nif);
	    	manager.setContractTermination(contractTermination);
    }
    
    public void deactivateUniverseManager(Long userProfileId) throws InstanceNotFoundException {
    	UniverseManager manager = findUniverseManager(userProfileId);
    	
    	Calendar today = getToday();
    	
    	manager.setContractTermination(today);
    	if (manager.getUniverse()!=null) { 
	    	if (manager.getUniverse().getManager() == manager) {
	    		manager.getUniverse().setManager(null);
	    	}
    	}
    	manager.setUniverse(null);
    }
    
    public Director registerDirector(String loginName, String clearPassword,
            UserProfileDetails userProfileDetails)
            throws DuplicateInstanceException, InstanceNotFoundException {

        try {
            userProfileDao.findByLoginName(loginName);
            throw new DuplicateInstanceException(loginName,
                    UserProfile.class.getName());
        } catch (InstanceNotFoundException e) {
            String encryptedPassword = PasswordEncrypter.crypt(clearPassword);
            Store store = storeDao.find(userProfileDetails.getStore());

            Director director = new Director(loginName,
                    encryptedPassword, userProfileDetails.getFirstName(),
                    userProfileDetails.getLastName(), userProfileDetails
                        .getEmail(), userProfileDetails
                        .getNif(), store);

            directorDao.save(director);
            return director;
        }

    }
    
    public void updateDirector(Long userProfileId, String firstName,
    		String lastName, String email, String nif)
            throws InstanceNotFoundException {

	    	Director director = findDirector(userProfileId);
	    	director.setFirstName(firstName);
	    	director.setLastName(lastName);
	    	director.setEmail(email);
	    	director.setNif(nif);
    }
    
    public void deactivateDirector(Long userProfileId) throws InstanceNotFoundException {
    	
    	Director director = findDirector(userProfileId);
    	Calendar today = getToday();
    	director.setDeletionDate(today);
    	
    }
    

    @Transactional(readOnly = true)
    public UserProfile login(String loginName, String password,
            boolean passwordIsEncrypted) throws InstanceNotFoundException,
            IncorrectPasswordException, ExpiredContractException {

        UserProfile userProfile = userProfileDao.findByLoginName(loginName);
        String storedPassword = userProfile.getEncryptedPassword();

        if (passwordIsEncrypted) {
            if (!password.equals(storedPassword)) {
                throw new IncorrectPasswordException(loginName);
            }
        } else {
            if (!PasswordEncrypter.isClearPasswordCorrect(password,
                    storedPassword)) {
                throw new IncorrectPasswordException(loginName);
            }
        }
        
        Calendar current = Calendar.getInstance();
        
        if (userProfile instanceof Receptionist) {
        	
        	Receptionist receptionist = (Receptionist) userProfile;
        	if (receptionist.getContractTermination().before(current)) {
        		throw new ExpiredContractException(loginName);
        	} else return receptionist;
        	
        } else if (userProfile instanceof Vendor) {
        	
        	Vendor vendor = (Vendor) userProfile;
        	if (vendor.getContractTermination().before(current)) {
        		throw new ExpiredContractException(loginName);
        	} else return vendor;
        	
        } else if (userProfile instanceof UniverseManager) {
        	
        	UniverseManager manager = (UniverseManager) userProfile;
        	if (manager.getContractTermination().before(current)) {
        		throw new ExpiredContractException(loginName);
        	} else return manager;
        	
        } else if (userProfile instanceof Director) {
        	
        	Director director = (Director) userProfile;
        	if (director.getDeletionDate() != null) {
        		if (director.getDeletionDate().before(current)) {
        			throw new ExpiredContractException(loginName);
        		}
        	} else return director;
        	
        } else if (userProfile instanceof Admin) {
        	
        	Admin admin = (Admin) userProfile;
        	if (admin.getDeletionDate() != null) {
        		if (admin.getDeletionDate().before(current)) {
        			throw new ExpiredContractException(loginName);
        		}
        	} else return admin;
        }
        
        return null;
    }

    @Transactional(readOnly = true)
    public UserProfile findUserProfile(Long userProfileId)
            throws InstanceNotFoundException {

        return userProfileDao.find(userProfileId);
    }
    
    @Transactional(readOnly = true)
    public Receptionist findReceptionist(Long userProfileId)
            throws InstanceNotFoundException {
    	
        return receptionistDao.find(userProfileId);
    }
    
    @Transactional(readOnly = true)
    public Vendor findVendor(Long userProfileId)
            throws InstanceNotFoundException {
    	
        return vendorDao.find(userProfileId);
    }
    
    @Transactional(readOnly = true)
    public UniverseManager findUniverseManager(Long userProfileId)
            throws InstanceNotFoundException {
    	
        return universeManagerDao.find(userProfileId);
    }
    
    @Transactional(readOnly = true)
    public Director findDirector(Long userProfileId)
            throws InstanceNotFoundException {
    	
        return directorDao.find(userProfileId);
    }
    
    @Transactional(readOnly = true)
    public Admin findAdmin(Long userProfileId)
            throws InstanceNotFoundException {
    	
        return adminDao.find(userProfileId);
    }

    public void updateUserProfileDetails(Long userProfileId,
            UserProfileDetails userProfileDetails)
            throws InstanceNotFoundException {

        UserProfile userProfile = userProfileDao.find(userProfileId);
        userProfile.setFirstName(userProfileDetails.getFirstName());
        userProfile.setLastName(userProfileDetails.getLastName());
        userProfile.setEmail(userProfileDetails.getEmail());

    }

    public void changePassword(Long userProfileId, String oldClearPassword,
            String newClearPassword) throws IncorrectPasswordException,
            InstanceNotFoundException {

        UserProfile userProfile;
        userProfile = userProfileDao.find(userProfileId);

        String storedPassword = userProfile.getEncryptedPassword();

        if (!PasswordEncrypter.isClearPasswordCorrect(oldClearPassword,
                storedPassword)) {
            throw new IncorrectPasswordException(userProfile.getLoginName());
        }

        userProfile.setEncryptedPassword(PasswordEncrypter
                .crypt(newClearPassword));

    }
    
    public boolean moveVendor(Long vendorId, Long universeId)
    		throws InstanceNotFoundException {
    	Vendor vendor = findVendor(vendorId);
    	Universe universe = universeDao.find(universeId);
    	if (vendor.getUniverse()!=universe) {
    		vendor.getUniverse().removeVendor(vendor);
    		universe.addVendor(vendor);
    		return true;
    	}
    	return false;
    }

	public boolean moveUniverseManager(Long managerId, Long universeId)
			throws InstanceNotFoundException {
		UniverseManager manager = findUniverseManager(managerId);
		Universe universe = universeDao.find(universeId);
		Universe oldUniverse = manager.getUniverse();
		UniverseManager oldManager = universe.getManager();
		if (oldUniverse != universe) {
			if (oldUniverse != null) {
				oldUniverse.setManager(null);
			}
			if (oldManager != null) {
				oldManager.setUniverse(null);
			}
			manager.setUniverse(universe);
			universe.setManager(manager);
			return true;
		}
		return false;
	}
	
	@Transactional(readOnly = true)
	public Long[] getManagerStats(Long userProfileId)
			throws InstanceNotFoundException {
	
		UniverseManager manager = findUniverseManager(userProfileId);
		Calendar[] dates = getStartEndDates();
		Long[] managerStats = new Long[3];
		managerStats[0] = shiftDao.getTotalShiftsDuration(manager.getUserProfileId(), dates[0], dates[1]);
		managerStats[1] = shiftDao.getTotalShiftsDuration(manager.getUserProfileId(), dates[2], dates[3]);
		managerStats[2] = shiftDao.getTotalShiftsDuration(manager.getUserProfileId(), dates[4], dates[5]);
		return managerStats;	
	}
	
	public List<VendorUniverseStatsDTO> getVendorTasksStats(Long userProfileId)
    		throws InstanceNotFoundException {
		
		VendorRowStatsDTO row;
		VendorUniverseStatsDTO universeStats;
    	List<VendorUniverseStatsDTO> totalResult = new ArrayList<VendorUniverseStatsDTO>();
    	
    	Vendor vendor = findVendor(userProfileId);
    	List<Universe> universes = new ArrayList<Universe>();
    	universes.addAll(storeDao.find(vendor.getStore().getStoreId()).getUniverses());
    	Calendar[] dates = getStartEndDates();
    	
    	for (Universe universe : universes) {
    		universeStats = new VendorUniverseStatsDTO();
    		List<VendorRowStatsDTO> universeResult = new ArrayList<VendorRowStatsDTO>();
    		
	    	List<TypeAndMillisDTO> weekStats = taskDao.getVendorTasksDuration(userProfileId, universe.getUniverseId(), dates[0], dates[1]);
	    	List<TypeAndMillisDTO> monthStats = taskDao.getVendorTasksDuration(userProfileId, universe.getUniverseId(), dates[2], dates[3]);
	    	List<TypeAndMillisDTO> trimesterStats = taskDao.getVendorTasksDuration(userProfileId, universe.getUniverseId(), dates[4], dates[5]);
	    	
	    	for (TypeAndMillisDTO trimester : trimesterStats) {
	    		row = new VendorRowStatsDTO();
	    		row.setTaskType(trimester.getType());
	    		if (trimester.getMillis() != null) {
	    			row.setTrimesterMillis(trimester.getMillis());
	    		} else {
	    			row.setTrimesterMillis((long)0);
	    		}
	    		for (TypeAndMillisDTO month : monthStats) {
	    			if (month.getType()==row.getTaskType()) {
	    				if (month.getMillis() != null) {
	    				row.setMonthMillis(month.getMillis());
	    				} else {
	    					row.setMonthMillis((long) 0);
	    				}
	    			}
	    		}
	    		for (TypeAndMillisDTO week : weekStats) {
	    			if (week.getType()==row.getTaskType()) {
	    				if (week.getMillis() != null) {
	    					row.setWeekMillis(week.getMillis());
	    				} else {
	    					row.setWeekMillis((long) 0);
	    				}
	    			}
	    		}
	    		universeResult.add(row);
	    	}
	    	
	    	universeStats.setUniverse(universe);
	    	universeStats.setRows(universeResult);
	    	if (!universeStats.getRows().isEmpty()) {
	    		totalResult.add(universeStats);
	    	}
	    	
    	}
    	
    	return totalResult;
    	
	}
    
    public List<VendorRowStatsDTO> getVendorMissionsStats(Long userProfileId)
    		throws InstanceNotFoundException {
    	
    	VendorRowStatsDTO row;
    	List<VendorRowStatsDTO> result = new ArrayList<VendorRowStatsDTO>();
    	
    	findVendor(userProfileId);
    	Calendar[] dates = getStartEndDates();
    	
    	List<TypeAndMillisDTO> weekStats = missionDao.getVendorMissionsDuration(userProfileId, dates[0], dates[1]);
    	List<TypeAndMillisDTO> monthStats = missionDao.getVendorMissionsDuration(userProfileId, dates[2], dates[3]);
    	List<TypeAndMillisDTO> trimesterStats = missionDao.getVendorMissionsDuration(userProfileId, dates[4], dates[5]);
    	
    	for (TypeAndMillisDTO trimester : trimesterStats) {
    		row = new VendorRowStatsDTO();
    		row.setTaskType(trimester.getType());
    		if (trimester.getMillis() != null) {
    			row.setTrimesterMillis(trimester.getMillis());
    		} else {
    			row.setTrimesterMillis((long)0);
    		}
    		for (TypeAndMillisDTO month : monthStats) {
    			if (month.getType()==row.getTaskType()) {
    				if (month.getMillis() != null) {
    				row.setMonthMillis(month.getMillis());
    				} else {
    					row.setMonthMillis((long) 0);
    				}
    			}
    		}
    		for (TypeAndMillisDTO week : weekStats) {
    			if (week.getType()==row.getTaskType()) {
    				if (week.getMillis() != null) {
    					row.setWeekMillis(week.getMillis());
    				} else {
    					row.setWeekMillis((long) 0);
    				}
    			}
    		}
    		result.add(row);
    	}
    	
    	return result;
    	

    }
	
	private Calendar[] getStartEndDates() {
		int year = Calendar.getInstance().get(Calendar.YEAR);
		int month = Calendar.getInstance().get(Calendar.MONTH);
		int week = Calendar.getInstance().get(Calendar.WEEK_OF_YEAR);
		
		Calendar startWeek = Calendar.getInstance();
		startWeek.clear();
		startWeek.set(Calendar.YEAR, year);
		startWeek.set(Calendar.WEEK_OF_YEAR, week);
		Calendar endWeek = (Calendar) startWeek.clone();
		endWeek.add(Calendar.WEEK_OF_YEAR, 1);
		
		Calendar startMonth = Calendar.getInstance();
		startMonth.clear();
		startMonth.set(Calendar.YEAR, year);
		startMonth.set(Calendar.MONTH, month);
		Calendar endMonth = (Calendar) startMonth.clone();
		endMonth.add(Calendar.MONTH, 1);
		
		switch (month) {
		case 0 : break;
		case 1 : month = 0; break;
		case 2 : month = 0; break;
		case 3 : break;
		case 4 : month = 3; break;
		case 5 : month = 3; break;
		case 6 : break;
		case 7 : month = 6; break;
		case 8 : month = 6; break;
		case 9 : break;
		case 10 : month = 9; break;
		case 11 : month = 9; break;
		}
		Calendar startTrimester = Calendar.getInstance();
		startTrimester.clear();
		startTrimester.set(Calendar.YEAR, year);
		startTrimester.set(Calendar.MONTH, month);
		Calendar endTrimester = (Calendar) startTrimester.clone();
		endTrimester.add(Calendar.MONTH, 3);
		
		Calendar[] dates = new Calendar[6];
		dates[0] = startWeek;
		dates[1] = endWeek;
		dates[2] = startMonth;
		dates[3] = endMonth;
		dates[4] = startTrimester;
		dates[5] = endTrimester;
		
		return dates;
	}
	
	public float numOfWeeksMonth() {
		Calendar[] dates = getStartEndDates();
		return numOfWeeks(dates[2], dates[3]);
	}
	
	public float numOfWeeksTrimester() {
		Calendar[] dates = getStartEndDates();
		return numOfWeeks(dates[4], dates[5]);
	}
	
	private float numOfWeeks(Calendar start, Calendar end) {
	    final Long DAY_IN_MILLIS = (long) (1000 * 60 * 60 * 24);
	    float numOfDays = (end.getTimeInMillis() - start.getTimeInMillis()) / DAY_IN_MILLIS;
	    return numOfDays/7;
	}
	
	private Calendar getToday() {
		
		Calendar today = Calendar.getInstance();
    	today.set(Calendar.HOUR_OF_DAY, 0);
    	today.set(Calendar.MINUTE, 0);
    	today.set(Calendar.SECOND, 0);
    	
    	return today;
	}
	
}
